#include <GL/gl.h>

#include <stdlib.h>
#include <math.h>

#include "battle.hpp"
#include "font.hpp"
#include "foreach.hpp"
#include "formatter.hpp"
#include "geometry.hpp"
#include "grid_widget.hpp"
#include "label.hpp"
#include "raster.hpp"
#include "scenario.hpp"
#include "unit_utils.hpp"

namespace game {

namespace {

int roll()
{
	int result = 0;
	int die = rand()%10;
	while(die == 9) {
		result += die;
		die = rand()%10;
	}

	result += die;
	return result;
}

bool calculate_hit(int attack, int defense)
{
	const int attack_roll = roll();
	const int defense_roll = roll();

	const bool result = (attack + attack_roll) > (defense + defense_roll);

	std::cerr << "ATTACK: " << attack << " + " << attack_roll << " vs " << defense << " + " << defense_roll << " -> " << (result ? "HIT" : "MISS") << "\n";

	return result;
}
}

class battle_unit
{
public:
	battle_unit(unit_ptr unit, point pos, int nattacks, bool face_left, int attack, int defense, int attack_speed, int initiative);
	void draw() const;

	int attacks_left() const { return attacks_; }

	void set_being_attacked() { ++attacked_by_; }
	void done_being_attacked() { --attacked_by_; }

	bool destroyed() const { return hitpoints_ <= 0; }

	void set_target(battle_unit_ptr target);
	bool has_target() const { return target_ && !target_->destroyed() || time_until_attack_ < 0; }

	void process();
	void get_hit();

	const point& pos() const { return pos_; }

	battle_projectile_ptr get_projectile() { battle_projectile_ptr res = projectile_; projectile_ = battle_projectile_ptr(); return res; }

private:
	unit_ptr unit_;
	battle_unit_ptr target_;
	int hitpoints_;
	int attacks_;
	int attack_speed_;
	int time_until_attack_;
	int initiative_;

	int attacked_by_;

	point pos_, base_pos_;
	bool face_left_;
	int attack_, defense_;

	//the location of the unit we just attacked; used so if we have lots
	//of attacks we can strike multiple opponents at once.
	point last_attack_;

	battle_projectile_ptr projectile_;
};

class battle_projectile
{
public:
	battle_projectile(battle_unit_ptr target, point pos, const unit_animation& anim, bool hits)
	  : target_(target), pos_(pos), anim_(anim), hits_(hits), destroyed_(false)
	{}

	void process();
	void draw() const;
	bool destroyed() const;
private:
	battle_unit_ptr target_;
	point pos_;
	const unit_animation& anim_;
	bool hits_;
	bool destroyed_;
};

battle_unit::battle_unit(unit_ptr unit, point pos, int nattacks, bool face_left, int attack, int defense, int attack_speed, int initiative)
  : unit_(unit), hitpoints_(is_commander(*unit) ? unit->hitpoints() : 1), attacks_(nattacks), attack_speed_(attack_speed), initiative_(initiative), time_until_attack_(rand()%30), attacked_by_(0), pos_(pos), base_pos_(pos), face_left_(face_left), attack_(attack), defense_(defense), last_attack_(-1, -1)
{
}

void battle_unit::draw() const
{
	const int color_index = unit_color_index(*unit_);
	if(destroyed()) {
		const unit_animation& anim = unit_->type().death_animation();
		if(time_until_attack_ > anim.team_animation(color_index).length()) {
			return;
		}

		const GLfloat proportion = 1.0 - time_until_attack_/GLfloat(anim.team_animation(color_index).length());

		GLfloat color[4];
		glGetFloatv(GL_CURRENT_COLOR, color);
		glColor4f(color[0], color[1], color[2], color[3]*proportion);
		anim.team_animation(color_index).draw(pos_.x, pos_.y, time_until_attack_, face_left_);
		glColor4fv(color);
		return;
	}

	const unit_animation* anim = &unit_->type().standing_animation();
	int time_in_anim = 0;

	if(time_until_attack_ < 0 && time_until_attack_ > -attack_speed_ + 6) {
		if(time_until_attack_ < -attack_speed_ + 18) {
			anim = &unit_->type().attack_animation();
			time_in_anim = -attack_speed_ + 18 - time_until_attack_;
		}
	} else if(attacked_by_ > 0) {
		anim = &unit_->type().defend_animation();
	}

	anim->team_animation(color_index).draw(pos_.x, pos_.y, time_in_anim, face_left_);
}

void battle_unit::set_target(battle_unit_ptr target)
{
	if(attacks_ > 0) {
		target_ = target;
		base_pos_ = pos_;
		time_until_attack_ = rand()%initiative_;
	}
}

void battle_unit::process()
{
	if(destroyed()) {
		time_until_attack_++;
		return;
	}

	//if we can immediately perform an attack on a target.
	if(last_attack_.x != -1 && attacks_ > 5 && target_ && !target_->destroyed() &&
	   abs(target_->pos_.x - last_attack_.x) < 6 &&
	   abs(target_->pos_.y - last_attack_.y) < 6) {
		--attacks_;
		if(calculate_hit(attack_, target_->defense_)) {
			target_->get_hit();
			if(target_->destroyed()) {
				last_attack_ = target_->pos();
			}
		}
	} else {
		last_attack_ = point(-1,-1);
	}

	//if we have to move toward our opponent.
	if(unit_->fighting_style() == unit_type::FIGHTING_STYLE_MELEE &&
	   attacks_ > 0 && target_ && std::abs(pos_.x - target_->pos_.x) > 100) {
		const int Speed = 2;
		if(target_->pos_.x > pos_.x) {
			pos_.x += Speed;
		} else {
			pos_.x -= Speed;
		}
		base_pos_.x = pos_.x;
	} else if(time_until_attack_ > 0) {
		--time_until_attack_;
	} else if(time_until_attack_ == 0) {
		if(attacks_ > 0 && target_) {
			--time_until_attack_;
			target_->set_being_attacked();
		}
	} else if(target_) {
		--time_until_attack_;
		if(time_until_attack_ == -attack_speed_) {

			--attacks_;

			const bool hits = calculate_hit(attack_, target_->defense_);
			if(unit_->type().missile_animation()) {
				projectile_ = battle_projectile_ptr(new battle_projectile(target_, pos_, *unit_->type().missile_animation(), hits));
			} else {
				if(hits) {
					target_->get_hit();
					if(target_->destroyed()) {
						last_attack_ = target_->pos();
					}
				}
			}

			target_->done_being_attacked();

			//make our base pos converge to the point we're at now
			base_pos_.y = (base_pos_.y + pos_.y)/2;
		}

		if(unit_->fighting_style() == unit_type::FIGHTING_STYLE_MELEE) {
			const int position = time_until_attack_ > -attack_speed_ ? -time_until_attack_ : attack_speed_*2 + time_until_attack_;
			const double ratio = position/GLfloat(attack_speed_);
			pos_.x = (1.0-ratio)*base_pos_.x + ratio*(target_->pos_.x + (face_left_ ? 20 : -20));
			pos_.y = (1.0-ratio)*base_pos_.y + ratio*target_->pos_.y;
		}

		if(time_until_attack_ <= -attack_speed_*2) {
			time_until_attack_ = rand()%initiative_;
		}
	}
}

void battle_unit::get_hit()
{
	if(!destroyed()) {
		--hitpoints_;
		unit_->get_hit();
		if(destroyed()) {
			if(time_until_attack_ < 0 && target_) {
				target_->done_being_attacked();
			}

			//use this as a counter to destruction.
			time_until_attack_ = 0;
		}
	}
}

void battle_projectile::process()
{
	if(destroyed()) {
		return;
	}

	const int MoveSpeed = 10;

	if(target_->pos().x - pos_.x) {
		pos_.y += (((100*(target_->pos().y - pos_.y))/abs(target_->pos().x - pos_.x))*MoveSpeed)/100;
	}

	if(target_->pos().x < pos_.x) {
		pos_.x -= MoveSpeed;
	} else {
		pos_.x += MoveSpeed;
	}

	if(abs(pos_.x - target_->pos().x) < MoveSpeed) {
		if(hits_ && !target_->destroyed()) {
			target_->get_hit();
		}

		destroyed_ = true;
	}
}

void battle_projectile::draw() const
{
	if(destroyed_) {
		return;
	}

	anim_.team_animation(0).draw(pos_.x, pos_.y, 0, target_->pos().x < pos_.x);
}

bool battle_projectile::destroyed() const
{
	return destroyed_;
}

namespace {
battle* current_battle;
const int CyclesAtEnd = 25;
}

battle* battle::current()
{
	return current_battle;
}

battle::battle(unit_ptr attacker, unit_ptr defender)
  : attacker_(attacker), defender_(defender),
	fade_(0.0), cycles_at_end_(CyclesAtEnd)
{
	std::vector<std::string>
	  attacker_attack_explanation, attacker_defense_explanation,
	  defender_attack_explanation, defender_defense_explanation;
	attacker_attack_ = calculate_unit_attack(*attacker, *defender, &attacker_attack_explanation);
	attacker_defense_ = calculate_unit_defense(*attacker, *defender, &attacker_defense_explanation);
	defender_attack_ = calculate_unit_attack(*defender, *attacker, &defender_attack_explanation);
	defender_defense_ = calculate_unit_defense(*defender, *attacker, &defender_defense_explanation);

	srand(time(NULL));

	const bool attacker_is_commander = is_commander(*attacker_);
	const bool defender_is_commander = is_commander(*defender_);

	int attacker_speed = attacker_is_commander ? 20 : 40;
	int defender_speed = defender_is_commander ? 20 : 40;

	const int top = 0;
	const int height = 300;
	const int attacker_xpos = (attacker->fighting_style() == unit_type::FIGHTING_STYLE_MELEE) ? 310 : 150;
	const int nattackers = (attacker_is_commander ? 1 : attacker->hitpoints());
	for(int n = 0; n != nattackers; ++n) {
		const int y_distance = height/(nattackers+1);
		attacker_units_.push_back(battle_unit_ptr(new battle_unit(attacker_, point(attacker_xpos + ((n&1) ? 10 : 0), top + (n+1)*y_distance), attacker->aggression(), false, attacker_attack_, attacker_defense_, attacker_speed, attacker_speed)));
	}

	const int defender_xpos = (defender->fighting_style() == unit_type::FIGHTING_STYLE_MELEE) ? 390 : 550;

	//calculate how many attacks the defender gets.
	int nattacks = attacker->aggression();
	if(hex::distance_between(attacker->loc(), defender->loc()) > defender->attack_range()) {
		nattacks = 0;
	} else if(defender->fighting_style() == unit_type::FIGHTING_STYLE_ARCHER || defender_is_commander) {
		//archers and commanders use their own aggression to determine attacks
		//on defense.
		nattacks = defender->aggression();
	} else if(attacker->fighting_style() == unit_type::FIGHTING_STYLE_ARCHER &&
	          defender->fighting_style() != unit_type::FIGHTING_STYLE_ARCHER) {
		//When attacked by an archer, a melee unit gets to use their own
		//aggression.
		nattacks = defender->aggression();
	}

	if(nattacks > 0 && attacker_is_commander && !defender_is_commander) {
		//against a leader we only get one attack each when defending.
		nattacks = 1;
	}

	const int ndefenders = (defender_is_commander ? 1 : defender->hitpoints());
	for(int n = 0; n != ndefenders; ++n) {

		const int y_distance = height/(ndefenders+1);
		defender_units_.push_back(battle_unit_ptr(new battle_unit(defender_, point(defender_xpos - ((n&1) ? 10 : 0), top + (n+1)*y_distance), nattacks, true, defender_attack_, defender_defense_, defender_speed, defender_speed)));
	}


	gui::grid* attacker_description_grid(new gui::grid(2));
	gui::grid* defender_description_grid(new gui::grid(2));

	attacker_description_grid->set_hpad(10);
	defender_description_grid->set_hpad(10);

	attacker_description_ = gui::widget_ptr(attacker_description_grid);
	defender_description_ = gui::widget_ptr(defender_description_grid);

	const int HeaderFontSize = 28, DetailsFontSize = 14;
	const SDL_Color& color = graphics::color_yellow();

	attacker_description_grid->add_col(gui::label::create("Attack", color, HeaderFontSize))
	                          .add_col(gui::label::create(formatter() << attacker_attack_, color, HeaderFontSize));
	foreach(const std::string& explanation, attacker_attack_explanation) {
		attacker_description_grid->add_col(gui::label::create(explanation, color, DetailsFontSize));
	}

	attacker_description_grid->add_col(gui::label::create("Defense", color, HeaderFontSize))
	                          .add_col(gui::label::create(formatter() << attacker_defense_, color, HeaderFontSize));
	foreach(const std::string& explanation, attacker_defense_explanation) {
		attacker_description_grid->add_col(gui::label::create(explanation, color, DetailsFontSize));
	}

	defender_description_grid->add_col(gui::label::create("Attack", color, HeaderFontSize))
	                          .add_col(gui::label::create(formatter() << defender_attack_, color, HeaderFontSize));
	foreach(const std::string& explanation, defender_attack_explanation) {
		defender_description_grid->add_col(gui::label::create(explanation, color, DetailsFontSize));
	}
	defender_description_grid->add_col(gui::label::create("Defense", color, HeaderFontSize))
	                          .add_col(gui::label::create(formatter() << defender_defense_, color, HeaderFontSize));
	foreach(const std::string& explanation, defender_defense_explanation) {
		defender_description_grid->add_col(gui::label::create(explanation, color, DetailsFontSize));
	}

	attacker_description_grid->set_loc(20, 20);
	defender_description_grid->set_loc(500, 20);
}

battle::~battle()
{
	if(current_battle == this) {
		current_battle = NULL;
	}
}

bool battle::done() const
{
	return cycles_at_end_ <= 0;
}

bool battle::fighting_finished() const
{
	int nattackers = 0, ndefenders = 0;
	int nattacks = 0, ndefends = 0;
	foreach(battle_unit_ptr u, attacker_units_) {
		if(u && !u->destroyed()) {
			++nattackers;
			nattacks += u->attacks_left();
		}
	}

	foreach(battle_unit_ptr u, defender_units_) {
		if(u && !u->destroyed()) {
			++ndefenders;
			ndefends += u->attacks_left();
		}
	}

	int nprojectiles = 0;
	foreach(battle_projectile_ptr p, projectiles_) {
		if(p && !p->destroyed()) {
			++nprojectiles;
		}
	}

	return nprojectiles == 0 && (nattackers == 0 || ndefenders == 0 || (nattacks + ndefends) == 0);
}

void battle::process()
{
	current_battle = this;

	if(fighting_finished()) {
		--cycles_at_end_;
		if(cycles_at_end_ == 0) {
			std::cerr << "FIRE END BATTLE\n";
			scenario::current()->fire("end_battle");
		}
	}

	foreach(battle_unit_ptr u, attacker_units_) {
		if(!u->has_target()) {
			u->set_target(find_target(u, defender_units_));
		}

		battle_projectile_ptr projectile = u->get_projectile();
		if(projectile) {
			projectiles_.push_back(projectile);
		}
	}
	
	foreach(battle_unit_ptr u, defender_units_) {
		if(!u->has_target()) {
			u->set_target(find_target(u, attacker_units_));
		}

		battle_projectile_ptr projectile = u->get_projectile();
		if(projectile) {
			projectiles_.push_back(projectile);
		}
	}

	foreach(battle_unit_ptr u, attacker_units_) {
		u->process();
	}

	foreach(battle_unit_ptr u, defender_units_) {
		u->process();
	}

	foreach(battle_projectile_ptr projectile, projectiles_) {
		projectile->process();
	}
}

void battle::draw() const
{
	const bool is_done = fighting_finished();
	if(!is_done && fade_ < 1.0) {
		fade_ += 0.05;
	} else if(is_done && fade_ > 0.0) {
		fade_ -= 0.02;
	}

	//draw the background for the battle.
	glDisable(GL_TEXTURE_2D);
	glColor4f(0.0, 0.0, 0.0, std::min<GLfloat>(0.7,fade_));
	glBegin(GL_QUADS);
	glVertex3f(0, 0, 0);
	glVertex3f(700, 0, 0);
	glVertex3f(700, 400, 0);
	glVertex3f(0, 400, 0);
	glEnd();

	glEnable(GL_TEXTURE_2D);

	glColor4f(1.0, 1.0, 1.0, fade_);

	foreach(battle_unit_ptr unit, attacker_units_) {
		unit->draw();
	}

	foreach(battle_unit_ptr unit, defender_units_) {
		unit->draw();
	}

	foreach(battle_projectile_ptr projectile, projectiles_) {
		projectile->draw();
	}

	attacker_description_->draw();
	defender_description_->draw();

	//display the side's hitpoints
	graphics::blit_texture(font::render_text(formatter() << attacker_->hitpoints(), graphics::color_yellow(), 42), attacker_description_->x(), attacker_description_->y() + attacker_description_->height());
	graphics::blit_texture(font::render_text(formatter() << defender_->hitpoints(), graphics::color_yellow(), 42), defender_description_->x(), defender_description_->y() + defender_description_->height());

	glColor4f(1.0, 1.0, 1.0, 1.0);
}

battle_unit_ptr battle::find_target(battle_unit_ptr attacker, const std::vector<battle_unit_ptr>& targets)
{
	battle_unit_ptr result;
	int best_result = 0;
	foreach(battle_unit_ptr target, targets) {
		if(!target || target->destroyed()) {
			continue;
		}
		const int distance = std::abs(target->pos().y - attacker->pos().y);
		if(!result || distance < best_result) {
			best_result = distance;
			result = target;
		}
	}

	return result;
}

variant battle::get_value(const std::string& key) const
{
	if(key == "attacker") {
		return variant(attacker_.get());
	} else if(key == "defender") {
		return variant(defender_.get());
	}

	return variant();
}

}
